/*-------------------------------------------------------------------------
 *
 * option.c
 *		  postgres_fdw 的 FDW 和 GUC 选项处理
 *
 * Portions Copyright (c) 2012-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		  contrib/postgres_fdw/option.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/reloptions.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
#include "commands/extension.h"
#include "libpq/libpq-be.h"
#include "postgres_fdw.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/varlena.h"

/*
 * 描述此包装器使用的对象的有效选项。
 */
typedef struct PgFdwOption
{
	const char *keyword;
	Oid			optcontext;		/* 选项可能出现的目录的OID */
	bool		is_libpq_opt;	/* 如果在libpq中使用，则为真 */
} PgFdwOption;

/*
 * postgres_fdw的有效选项。
 * 在InitPgFdwOptions中分配并填充。
 */
static PgFdwOption *postgres_fdw_options;

/*
 * libpq的有效选项。
 * 在InitPgFdwOptions中分配并填充。
 */
static PQconninfoOption *libpq_options;

/*
 * GUC参数
 */
char	   *pgfdw_application_name = NULL;

void		_PG_init(void);

/*
 * 辅助函数
 */
static void fc_InitPgFdwOptions(void);
static bool fc_is_valid_option(const char *fc_keyword, Oid fc_context);
static bool fc_is_libpq_option(const char *fc_keyword);

#include "miscadmin.h"

/*
 * 验证提供给使用postgres_fdw的外部数据包装器、服务器、
 * 用户映射或外部表的通用选项。
 *
 * 如果选项或其值被认为是无效的，则引发错误。
 */
PG_FUNCTION_INFO_V1(postgres_fdw_validator);

Datum postgres_fdw_validator(PG_FUNCTION_ARGS)
{
	List	   *fc_options_list = untransformRelOptions(PG_GETARG_DATUM(0));
	Oid			fc_catalog = PG_GETARG_OID(1);
	ListCell   *fc_cell;

	/* 如果我们还没有构建选项列表，就构建它们。 */
	fc_InitPgFdwOptions();

	/*
	 * 检查仅提供postgres_fdw支持的选项，并且为
	 * 当前对象类型允许的选项。
	 */
	foreach(fc_cell, fc_options_list)
	{
		DefElem    *fc_def = (DefElem *) lfirst(fc_cell);

		if (!fc_is_valid_option(fc_def->defname, fc_catalog))
		{
			/*
			 * 指定了未知选项，进行投诉。提供与该对象有效选项列表的提示。
			 */
			PgFdwOption *fc_opt;
			StringInfoData fc_buf;

			initStringInfo(&fc_buf);
			for (fc_opt = postgres_fdw_options; fc_opt->keyword; fc_opt++)
			{
				if (fc_catalog == fc_opt->optcontext)
					appendStringInfo(&fc_buf, "%s%s", (fc_buf.len > 0) ? ", " : "",
									 fc_opt->keyword);
			}

			ereport(ERROR,
					(errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
					 errmsg("invalid option \"%s\"", fc_def->defname),
					 fc_buf.len > 0
					 ? errhint("Valid options in this context are: %s",
							   fc_buf.data)
					 : errhint("There are no valid options in this context.")));
		}

		/*
		 * 当我们可以在没有任何上下文的情况下验证选项值时，进行验证。
		 */
		if (strcmp(fc_def->defname, "use_remote_estimate") == 0 ||
			strcmp(fc_def->defname, "updatable") == 0 ||
			strcmp(fc_def->defname, "truncatable") == 0 ||
			strcmp(fc_def->defname, "async_capable") == 0 ||
			strcmp(fc_def->defname, "parallel_commit") == 0 ||
			strcmp(fc_def->defname, "keep_connections") == 0)
		{
			/* 这些仅接受布尔值 */
			(void) defGetBoolean(fc_def);
		}
		else if (strcmp(fc_def->defname, "fdw_startup_cost") == 0 ||
				 strcmp(fc_def->defname, "fdw_tuple_cost") == 0)
		{
			/*
			 * 这些必须具有大于或等于零的浮点值。
			 */
			char	   *fc_value;
			double		fc_real_val;
			bool		fc_is_parsed;

			fc_value = defGetString(fc_def);
			fc_is_parsed = parse_real(fc_value, &fc_real_val, 0, NULL);

			if (!fc_is_parsed)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("invalid value for floating point option \"%s\": %s",
								fc_def->defname, fc_value)));

			if (fc_real_val < 0)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("\"%s\" must be a floating point value greater than or equal to zero",
								fc_def->defname)));
		}
		else if (strcmp(fc_def->defname, "extensions") == 0)
		{
			/* 检查列表语法，警告有关未安装扩展的事项 */
			(void) ExtractExtensionList(defGetString(fc_def), true);
		}
		else if (strcmp(fc_def->defname, "fetch_size") == 0 ||
				 strcmp(fc_def->defname, "batch_size") == 0)
		{
			char	   *fc_value;
			int			fc_int_val;
			bool		fc_is_parsed;

			fc_value = defGetString(fc_def);
			fc_is_parsed = parse_int(fc_value, &fc_int_val, 0, NULL);

			if (!fc_is_parsed)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("invalid value for integer option \"%s\": %s",
								fc_def->defname, fc_value)));

			if (fc_int_val <= 0)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("\"%s\" must be an integer value greater than zero",
								fc_def->defname)));
		}
		else if (strcmp(fc_def->defname, "password_required") == 0)
		{
			bool		fc_pw_required = defGetBoolean(fc_def);

			/*
			 * 只有超级用户可以在用户映射上设置此选项，或
			 * 修改设置此选项的用户映射。我们允许用户清除此选项
			 * 如果已设置 - 实际上，我们没有选择，因为在验证
			 * 修改时无法查看旧映射。
			 */
			if (!superuser() && !fc_pw_required)
				ereport(ERROR,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("password_required=false is superuser-only"),
						 errhint("User mappings with the password_required option set to false may only be created or modified by the superuser.")));
		}
		else if (strcmp(fc_def->defname, "sslcert") == 0 ||
				 strcmp(fc_def->defname, "sslkey") == 0)
		{
			/* 对用户映射上的sslcert / sslkey类似的处理 */
			if (fc_catalog == UserMappingRelationId && !superuser())
				ereport(ERROR,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("sslcert and sslkey are superuser-only"),
						 errhint("User mappings with the sslcert or sslkey options set may only be created or modified by the superuser.")));
		}
	}

	PG_RETURN_VOID();
}

/*
 * 初始化选项列表。
 */
static void fc_InitPgFdwOptions(void)
{
	int			fc_num_libpq_opts;
	PQconninfoOption *fc_lopt;
	PgFdwOption *fc_popt;

	/* 非libpq FDW特定的FDW选项 */
	static const PgFdwOption fc_non_libpq_options[] = {
		{"schema_name", ForeignTableRelationId, false},
		{"table_name", ForeignTableRelationId, false},
		{"column_name", AttributeRelationId, false},
		/* use_remote_estimate在服务器和表上均可用 */
		{"use_remote_estimate", ForeignServerRelationId, false},
		{"use_remote_estimate", ForeignTableRelationId, false},
		/* 成本因素 */
		{"fdw_startup_cost", ForeignServerRelationId, false},
		{"fdw_tuple_cost", ForeignServerRelationId, false},
		/* 可传输扩展 */
		{"extensions", ForeignServerRelationId, false},
		/* updatable在服务器和表上均可用 */
		{"updatable", ForeignServerRelationId, false},
		{"updatable", ForeignTableRelationId, false},
		/* truncatable在服务器和表上均可用 */
		{"truncatable", ForeignServerRelationId, false},
		{"truncatable", ForeignTableRelationId, false},
		/* fetch_size在服务器和表上均可用 */
		{"fetch_size", ForeignServerRelationId, false},
		{"fetch_size", ForeignTableRelationId, false},
		/* batch_size在服务器和表上均可用 */
		{"batch_size", ForeignServerRelationId, false},
		{"batch_size", ForeignTableRelationId, false},
		/* async_capable在服务器和表上均可用 */
		{"async_capable", ForeignServerRelationId, false},
		{"async_capable", ForeignTableRelationId, false},
		{"parallel_commit", ForeignServerRelationId, false},
		{"keep_connections", ForeignServerRelationId, false},
		{"password_required", UserMappingRelationId, false},

		/*
		 * sslcert和sslkey实际上是libpq选项，但我们在这里重复它们
		 * 以使其能够出现在外部服务器上下文（当我们生成libpq选项时）
		 * 和用户映射上下文（从这里）。
		 */
		{"sslcert", UserMappingRelationId, true},
		{"sslkey", UserMappingRelationId, true},

		{NULL, InvalidOid, false}
	};

	/* 防止冗余初始化。 */
	if (postgres_fdw_options)
		return;

	/*
	 * 获取有效的 libpq 选项列表。
	 *
	 * 为了避免不必要的工作，我们只获取一次列表，并在
	 * 本后端进程的生命周期内使用它。我们无需担心
	 * 内存上下文问题，因为 PQconndefaults 使用 malloc 分配。
	 */
	libpq_options = PQconndefaults();
	if (!libpq_options)			/* 假设失败的原因是 OOM */
		ereport(ERROR,
				(errcode(ERRCODE_FDW_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Could not get libpq's default connection options.")));

	/* 统计可用的libpq选项数量。 */
	fc_num_libpq_opts = 0;
	for (fc_lopt = libpq_options; fc_lopt->keyword; fc_lopt++)
		fc_num_libpq_opts++;

	/*
	 * 构建一个数组，其中包含所有有效的
	 * postgres_fdw 选项，通过将 FDW 特定选项附加到 libpq 选项。
	 *
	 * 我们在这里使用普通的 malloc 来分配 postgres_fdw_options，因为它
	 * 与后端进程的生存时间相同。此外，保持
	 * libpq_options 在内存中可以避免复制每个关键字
	 * 字符串。
	 */
	postgres_fdw_options = (PgFdwOption *)
		malloc(sizeof(PgFdwOption) * fc_num_libpq_opts +
			   sizeof(fc_non_libpq_options));
	if (postgres_fdw_options == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FDW_OUT_OF_MEMORY),
				 errmsg("out of memory")));

	fc_popt = postgres_fdw_options;
	for (fc_lopt = libpq_options; fc_lopt->keyword; fc_lopt++)
	{
		/* 隐藏调试选项，以及我们内部覆盖的设置。 */
		if (strchr(fc_lopt->dispchar, 'D') ||
			strcmp(fc_lopt->keyword, "fallback_application_name") == 0 ||
			strcmp(fc_lopt->keyword, "client_encoding") == 0)
			continue;

		/* 我们不必复制关键字字符串，如上所述。 */
		fc_popt->keyword = fc_lopt->keyword;

		/*
		 * “user”和任何秘密选项仅在用户映射中允许。
		 * 其他一切都是服务器选项。
		 */
		if (strcmp(fc_lopt->keyword, "user") == 0 || strchr(fc_lopt->dispchar, '*'))
			fc_popt->optcontext = UserMappingRelationId;
		else
			fc_popt->optcontext = ForeignServerRelationId;
		fc_popt->is_libpq_opt = true;

		fc_popt++;
	}

	/* 附加 FDW 特定选项和虚拟终止符。 */
	memcpy(fc_popt, fc_non_libpq_options, sizeof(fc_non_libpq_options));
}

/*
 * 检查给定选项是否为有效的 postgres_fdw 选项之一。
 * context 是持有该选项所针对的对象的目录 Oid。
 */
static bool fc_is_valid_option(const char *fc_keyword, Oid fc_context)
{
	PgFdwOption *fc_opt;

	Assert(postgres_fdw_options);	/* 必须已经初始化 */

	for (fc_opt = postgres_fdw_options; fc_opt->keyword; fc_opt++)
	{
		if (fc_context == fc_opt->optcontext && strcmp(fc_opt->keyword, fc_keyword) == 0)
			return true;
	}

	return false;
}

/*
 * 检查给定选项是否为有效的 libpq 选项之一。
 */
static bool fc_is_libpq_option(const char *fc_keyword)
{
	PgFdwOption *fc_opt;

	Assert(postgres_fdw_options);	/* 必须已经初始化 */

	for (fc_opt = postgres_fdw_options; fc_opt->keyword; fc_opt++)
	{
		if (fc_opt->is_libpq_opt && strcmp(fc_opt->keyword, fc_keyword) == 0)
			return true;
	}

	return false;
}

/*
 * 生成只包含 libpq 选项的键值数组，
 * 来自给定的列表（可以包含任何类型的选项）。调用者必须分配
 * 足够大的数组。返回找到的选项数量。
 */
int ExtractConnectionOptions(List *fc_defelems, const char **fc_keywords,
						 const char **fc_values)
{
	ListCell   *fc_lc;
	int			fc_i;

	/* 如果我们还没有构建选项列表，就构建它们。 */
	fc_InitPgFdwOptions();

	fc_i = 0;
	foreach(fc_lc, fc_defelems)
	{
		DefElem    *fc_d = (DefElem *) lfirst(fc_lc);

		if (fc_is_libpq_option(fc_d->defname))
		{
			fc_keywords[fc_i] = fc_d->defname;
			fc_values[fc_i] = defGetString(fc_d);
			fc_i++;
		}
	}
	return fc_i;
}

/*
 * 解析以逗号分隔的字符串并返回一个包含字符串中
 * 命名扩展的 OID 的列表。如果列表中的任何名称无法
 * 找到，如果 warnOnMissing 为真，则报告警告，否则仅静默
 * 忽略它们。
 */
List * ExtractExtensionList(const char *fc_extensionsString, bool fc_warnOnMissing)
{
	List	   *fc_extensionOids = NIL;
	List	   *fc_extlist;
	ListCell   *fc_lc;

	/* SplitIdentifierString 侵入其输入，因此首先 pstrdup */
	if (!SplitIdentifierString(pstrdup(fc_extensionsString), ',', &fc_extlist))
	{
		/* 名称列表中的语法错误 */
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("parameter \"%s\" must be a list of extension names",
						"extensions")));
	}

	foreach(fc_lc, fc_extlist)
	{
		const char *fc_extension_name = (const char *) lfirst(fc_lc);
		Oid			fc_extension_oid = get_extension_oid(fc_extension_name, true);

		if (OidIsValid(fc_extension_oid))
		{
			fc_extensionOids = lappend_oid(fc_extensionOids, fc_extension_oid);
		}
		else if (fc_warnOnMissing)
		{
			ereport(WARNING,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("extension \"%s\" is not installed",
							fc_extension_name)));
		}
	}

	list_free(fc_extlist);
	return fc_extensionOids;
}

/*
 * 用状态信息替换给定 application_name 中以 % 字符开头的转义序列，并返回它。
 *
 * 此函数始终返回一个 palloc'd 字符串，因此调用者
 * 负责 pfree 它。
 */
char * process_pgfdw_appname(const char *fc_appname)
{
	const char *fc_p;
	StringInfoData fc_buf;

	initStringInfo(&fc_buf);

	for (fc_p = fc_appname; *fc_p != '\0'; fc_p++)
	{
		if (*fc_p != '%')
		{
			/* 字面字符，仅复制 */
			appendStringInfoChar(&fc_buf, *fc_p);
			continue;
		}

		/* 必须是 '%', 所以跳到下一个字符 */
		fc_p++;
		if (*fc_p == '\0')
			break;				/* 格式错误 - 忽略它 */
		else if (*fc_p == '%')
		{
			/* 字符串包含 %% */
			appendStringInfoChar(&fc_buf, '%');
			continue;
		}

		/* 处理选项 */
		switch (*fc_p)
		{
			case 'a':
				appendStringInfoString(&fc_buf, application_name);
				break;
			case 'c':
				appendStringInfo(&fc_buf, "%" INT64_MODIFIER "x.%x", MyStartTime, MyProcPid);
				break;
			case 'C':
				appendStringInfoString(&fc_buf, cluster_name);
				break;
			case 'd':
				if (MyProcPort)
				{
					const char *fc_dbname = MyProcPort->database_name;

					if (fc_dbname)
						appendStringInfoString(&fc_buf, fc_dbname);
					else
						appendStringInfoString(&fc_buf, "[unknown]");
				}
				break;
			case 'p':
				appendStringInfo(&fc_buf, "%d", MyProcPid);
				break;
			case 'u':
				if (MyProcPort)
				{
					const char *fc_username = MyProcPort->user_name;

					if (fc_username)
						appendStringInfoString(&fc_buf, fc_username);
					else
						appendStringInfoString(&fc_buf, "[unknown]");
				}
				break;
			default:
				/* 格式错误 - 忽略它 */
				break;
		}
	}

	return fc_buf.data;
}

/*
 * 模块加载回调
 */
void _PG_init(void)
{
	/*
	 * 与 application_name GUC 不同，不要设置 GUC_IS_NAME 标志，也不要 check_hook
	 * 允许 postgres_fdw.application_name 为超过
	 * NAMEDATALEN 字符的任何字符串，并且可以包含非 ASCII 字符。相反，
	 * 远程服务器将远程连接的 application_name 截断为少于
	 * NAMEDATALEN，并用 '?' 字符替换其中的任何非 ASCII 字符。
	 */
	DefineCustomStringVariable("postgres_fdw.application_name",
							   "Sets the application name to be used on the remote server.",
							   NULL,
							   &pgfdw_application_name,
							   NULL,
							   PGC_USERSET,
							   0,
							   NULL,
							   NULL,
							   NULL);

	MarkGUCPrefixReserved("postgres_fdw");
}
